package org.ouwh.fw.model.service;

import java.io.File;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;

import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;

import org.ouwh.fw.model.ModelCore;
import org.ouwh.fw.model.data.Struct;
import org.ouwh.fw.util.Base64;
import org.ouwh.fw.util.Util;

public abstract class Service extends NotificationBroadcasterSupport implements ServiceMBean {

	private static final String DOT_SERVICE = ".service";

	private final static int STOPPED = 0;

	private final static int STARTING_OR_STOPPING = 1;

	private final static int STARTED = 2;

	private String id;

	private String type;

	private AtomicInteger status = new AtomicInteger(STOPPED);

	public int getStatus() {
		return status.get();
	}

	protected abstract void init(Properties properties);

	protected abstract void startup() throws Exception;

	protected abstract void reset(Properties properties) throws Exception;

	protected abstract void shutdown() throws Exception;

	protected abstract void dumpToStruct(Struct struct);

	public Struct dumpToStruct() {
		Struct struct = new Struct(getId());
		struct.addData("type", getType());
		dumpToStruct(struct);
		return struct;
	}

	public final void start() throws Exception {
		if (status.compareAndSet(STOPPED, STARTING_OR_STOPPING)) {
			startup();
			status.set(STARTED);
		} else if (status.get() == STARTING_OR_STOPPING) {
			throw new Exception("Service is starting or stopping.");
		}
	}

	@Override
	public final void stop() throws Exception {
		if (status.compareAndSet(STARTED, STARTING_OR_STOPPING)) {
			shutdown();
			status.set(STOPPED);
		} else if (status.get() == STARTING_OR_STOPPING) {
			throw new Exception("Service is starting or stopping.");
		}
	}

	@Override
	public final void changeProperty(String key, String value) throws Exception {
		String path = getConfigPath();
		Properties props = Util.loadProperties(path);
		if (props.containsKey(key)) {
			props.setProperty(key, value);
			Util.storeProperties(path, props);
		} else {
			throw new Exception("no such config name");
		}
	}

	protected String getConfigPath() {
		String path = ModelCore.getConfigPath() + File.separator + id + DOT_SERVICE;
		return path;
	}

	@Override
	public final void encodeProperty(String key, String value) throws Exception {
		String path = getConfigPath();
		Properties props = Util.loadProperties(path);
		if (props.containsKey(key)) {
			Base64 base64 = new Base64();
			props.setProperty(key, "${enc-pwd:" + new String(base64.encode(turn(value.getBytes()))) + "}");
			Util.storeProperties(path, props);
		} else {
			throw new Exception("no such config name");
		}
	}

	@Override
	public final void hotReset() throws Exception {
		if (getStatus() == STARTED) {
			String path = getConfigPath();
			reset(Util.loadProperties(path));
		}
	}

	@Override
	public final String dump() {
		Struct struct = new Struct("service");
		struct.addData("id", getId());
		struct.addData("type", getType());
		try {
			dumpToStruct(struct);
		} catch (Exception e) {
		}
		return struct.toString();
	}

	@Override
	public final void addNotificationListener(NotificationListener listener, NotificationFilter filter,
			Object handback) {
		super.addNotificationListener(listener, filter, handback);
	}

	@Override
	public final void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException {
		super.removeNotificationListener(listener);
	}

	@Override
	public final void removeNotificationListener(NotificationListener listener, NotificationFilter filter,
			Object handback) throws ListenerNotFoundException {
		super.removeNotificationListener(listener, filter, handback);
	}

	@Override
	public final MBeanNotificationInfo[] getNotificationInfo() {
		return super.getNotificationInfo();
	}

	@Override
	public final void sendNotification(Notification notification) {
		super.sendNotification(notification);
	}

	@Override
	protected final void handleNotification(NotificationListener listener, Notification notif, Object handback) {
		super.handleNotification(listener, notif, handback);
	}

	public final String getId() {
		return id;
	}

	public final String getType() {
		return type;
	}

	final void setId(String id) {
		this.id = id;
	}

	final void setType(String type) {
		this.type = type;
	}

	public String toString() {
		StringBuffer tmp = new StringBuffer();
		tmp.append("service[");
		tmp.append(getId());
		tmp.append("], type[");
		tmp.append(getType());
		tmp.append("]");
		return tmp.toString();
	}

	protected String getPassword(Properties props, String key) throws Exception {
		String password = props.getProperty(key);
		Base64 base64 = new Base64();
		if (password.startsWith("${enc-pwd:") && password.endsWith("}")) {
			password = new String(
					turn(base64.decode(password.substring("${enc-pwd:".length(), password.length() - 1).getBytes())));
		} else {
			String path = ModelCore.getConfigPath() + File.separator + getId() + DOT_SERVICE;
			props.setProperty(key, "${enc-pwd:" + new String(base64.encode(turn(password.getBytes()))) + "}");
			try {
				Util.storeProperties(path, props);
			} finally {
				props.setProperty(key, password);
			}
		}
		return password;
	}

	private byte[] turn(byte[] src) {
		byte[] des = new byte[src.length];
		for (int i = 0; i < src.length; i++)
			des[i] = (byte) (0x5f ^ src[i]);
		return des;
	}

}
